/** @file
  MMC/SD Card driver for OMAP 35xx (SDIO not supported)

  This driver always produces a BlockIo protocol but it starts off with no Media
  present. A TimerCallBack detects when media is inserted or removed and after
  a media change event a call to BlockIo ReadBlocks/WriteBlocks will cause the
  media to be detected (or removed) and the BlockIo Media structure will get
  updated. No MMC/SD Card harward registers are updated until the first BlockIo
  ReadBlocks/WriteBlocks after media has been insterted (booting with a card
  plugged in counts as an insertion event).

  Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "MMCHS.h"

EFI_BLOCK_IO_MEDIA gMMCHSMedia = {
  SIGNATURE_32('s','d','i','o'),            // MediaId
  TRUE,                                     // RemovableMedia
  FALSE,                                    // MediaPresent
  FALSE,                                    // LogicalPartition
  FALSE,                                    // ReadOnly
  FALSE,                                    // WriteCaching
  512,                                      // BlockSize
  4,                                        // IoAlign
  0,                                        // Pad
  0                                         // LastBlock
};

typedef struct {
  VENDOR_DEVICE_PATH  Mmc;
  EFI_DEVICE_PATH     End;
} MMCHS_DEVICE_PATH;

MMCHS_DEVICE_PATH gMmcHsDevicePath = {
  {
    {
      HARDWARE_DEVICE_PATH,
      HW_VENDOR_DP,
      {
        (UINT8)(sizeof(VENDOR_DEVICE_PATH)),
        (UINT8)((sizeof(VENDOR_DEVICE_PATH)) >> 8),
      },
    },
    { 0xb615f1f5, 0x5088, 0x43cd, { 0x80, 0x9c, 0xa1, 0x6e, 0x52, 0x48, 0x7d, 0x00 } },
  },
  {
    END_DEVICE_PATH_TYPE,
    END_ENTIRE_DEVICE_PATH_SUBTYPE,
    { sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 }
  }
};

CARD_INFO                  gCardInfo;
EMBEDDED_EXTERNAL_DEVICE   *gTPS65950;
EFI_EVENT                  gTimerEvent;
BOOLEAN                    gMediaChange = FALSE;

//
// Internal Functions
//


VOID
ParseCardCIDData (
  UINT32 Response0,
  UINT32 Response1,
  UINT32 Response2,
  UINT32 Response3
  )
{
  gCardInfo.CIDData.MDT = ((Response0 >> 8) & 0xFFF);
  gCardInfo.CIDData.PSN = (((Response0 >> 24) & 0xFF) | ((Response1 & 0xFFFFFF) << 8));
  gCardInfo.CIDData.PRV = ((Response1 >> 24) & 0xFF);
  gCardInfo.CIDData.PNM[4] = ((Response2) & 0xFF);
  gCardInfo.CIDData.PNM[3] = ((Response2 >> 8) & 0xFF);
  gCardInfo.CIDData.PNM[2] = ((Response2 >> 16) & 0xFF);
  gCardInfo.CIDData.PNM[1] = ((Response2 >> 24) & 0xFF);
  gCardInfo.CIDData.PNM[0] = ((Response3) & 0xFF);
  gCardInfo.CIDData.OID = ((Response3 >> 8) & 0xFFFF);
  gCardInfo.CIDData.MID = ((Response3 >> 24) & 0xFF);
}


VOID
UpdateMMCHSClkFrequency (
  UINTN NewCLKD
  )
{
  //Set Clock enable to 0x0 to not provide the clock to the card
  MmioAnd32 (MMCHS_SYSCTL, ~CEN);

  //Set new clock frequency.
  MmioAndThenOr32 (MMCHS_SYSCTL, ~CLKD_MASK, NewCLKD << 6);

  //Poll till Internal Clock Stable
  while ((MmioRead32 (MMCHS_SYSCTL) & ICS_MASK) != ICS);

  //Set Clock enable to 0x1 to provide the clock to the card
  MmioOr32 (MMCHS_SYSCTL, CEN);
}


EFI_STATUS
SendCmd (
  UINTN Cmd,
  UINTN CmdInterruptEnableVal,
  UINTN CmdArgument
  )
{
  UINTN MmcStatus;
  UINTN RetryCount = 0;

  //Check if command line is in use or not. Poll till command line is available.
  while ((MmioRead32 (MMCHS_PSTATE) & DATI_MASK) == DATI_NOT_ALLOWED);

  //Provide the block size.
  MmioWrite32 (MMCHS_BLK, BLEN_512BYTES);

  //Setting Data timeout counter value to max value.
  MmioAndThenOr32 (MMCHS_SYSCTL, ~DTO_MASK, DTO_VAL);

  //Clear Status register.
  MmioWrite32 (MMCHS_STAT, 0xFFFFFFFF);

  //Set command argument register
  MmioWrite32 (MMCHS_ARG, CmdArgument);

  //Enable interrupt enable events to occur
  MmioWrite32 (MMCHS_IE, CmdInterruptEnableVal);

  //Send a command
  MmioWrite32 (MMCHS_CMD, Cmd);

  //Check for the command status.
  while (RetryCount < MAX_RETRY_COUNT) {
    do {
      MmcStatus = MmioRead32 (MMCHS_STAT);
    } while (MmcStatus == 0);

    //Read status of command response
    if ((MmcStatus & ERRI) != 0) {

      //Perform soft-reset for mmci_cmd line.
      MmioOr32 (MMCHS_SYSCTL, SRC);
      while ((MmioRead32 (MMCHS_SYSCTL) & SRC));

      DEBUG ((EFI_D_INFO, "MmcStatus: %x\n", MmcStatus));
      return EFI_DEVICE_ERROR;
    }

    //Check if command is completed.
    if ((MmcStatus & CC) == CC) {
      MmioWrite32 (MMCHS_STAT, CC);
      break;
    }

    RetryCount++;
  }

  if (RetryCount == MAX_RETRY_COUNT) {
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}


VOID
GetBlockInformation (
  UINTN *BlockSize,
  UINTN *NumBlocks
  )
{
  CSD_SDV2 *CsdSDV2Data;
  UINTN    CardSize;

  if (gCardInfo.CardType == SD_CARD_2_HIGH) {
    CsdSDV2Data = (CSD_SDV2 *)&gCardInfo.CSDData;

    //Populate BlockSize.
    *BlockSize = (0x1UL << CsdSDV2Data->READ_BL_LEN);

    //Calculate Total number of blocks.
    CardSize = CsdSDV2Data->C_SIZELow16 | (CsdSDV2Data->C_SIZEHigh6 << 2);
    *NumBlocks = ((CardSize + 1) * 1024);
  } else {
    //Populate BlockSize.
    *BlockSize = (0x1UL << gCardInfo.CSDData.READ_BL_LEN);

    //Calculate Total number of blocks.
    CardSize = gCardInfo.CSDData.C_SIZELow2 | (gCardInfo.CSDData.C_SIZEHigh10 << 2);
    *NumBlocks = (CardSize + 1) * (1 << (gCardInfo.CSDData.C_SIZE_MULT + 2));
  }

  //For >=2G card, BlockSize may be 1K, but the transfer size is 512 bytes.
  if (*BlockSize > 512) {
    *NumBlocks = MultU64x32(*NumBlocks, *BlockSize/2);
    *BlockSize = 512;
  }

  DEBUG ((EFI_D_INFO, "Card type: %x, BlockSize: %x, NumBlocks: %x\n", gCardInfo.CardType, *BlockSize, *NumBlocks));
}


VOID
CalculateCardCLKD (
  UINTN *ClockFrequencySelect
  )
{
  UINT8    MaxDataTransferRate;
  UINTN    TransferRateValue = 0;
  UINTN    TimeValue = 0 ;
  UINTN    Frequency = 0;

  MaxDataTransferRate = gCardInfo.CSDData.TRAN_SPEED;

  // For SD Cards  we would need to send CMD6 to set
  // speeds abouve 25MHz. High Speed mode 50 MHz and up

  //Calculate Transfer rate unit (Bits 2:0 of TRAN_SPEED)
  switch (MaxDataTransferRate & 0x7) {
    case 0:
      TransferRateValue = 100 * 1000;
      break;

    case 1:
      TransferRateValue = 1 * 1000 * 1000;
      break;

    case 2:
      TransferRateValue = 10 * 1000 * 1000;
      break;

    case 3:
      TransferRateValue = 100 * 1000 * 1000;
      break;

    default:
      DEBUG((EFI_D_ERROR, "Invalid parameter.\n"));
      ASSERT(FALSE);
  }

  //Calculate Time value (Bits 6:3 of TRAN_SPEED)
  switch ((MaxDataTransferRate >> 3) & 0xF) {
    case 1:
      TimeValue = 10;
      break;

    case 2:
      TimeValue = 12;
      break;

    case 3:
      TimeValue = 13;
      break;

    case 4:
      TimeValue = 15;
      break;

    case 5:
      TimeValue = 20;
      break;

    case 6:
      TimeValue = 25;
      break;

    case 7:
      TimeValue = 30;
      break;

    case 8:
      TimeValue = 35;
      break;

    case 9:
      TimeValue = 40;
      break;

    case 10:
      TimeValue = 45;
      break;

    case 11:
      TimeValue = 50;
      break;

    case 12:
      TimeValue = 55;
      break;

    case 13:
      TimeValue = 60;
      break;

    case 14:
      TimeValue = 70;
      break;

    case 15:
      TimeValue = 80;
      break;

    default:
      DEBUG((EFI_D_ERROR, "Invalid parameter.\n"));
      ASSERT(FALSE);
  }

  Frequency = TransferRateValue * TimeValue/10;

  //Calculate Clock divider value to program in MMCHS_SYSCTL[CLKD] field.
  *ClockFrequencySelect = ((MMC_REFERENCE_CLK/Frequency) + 1);

  DEBUG ((EFI_D_INFO, "MaxDataTransferRate: 0x%x, Frequency: %d KHz, ClockFrequencySelect: %x\n", MaxDataTransferRate, Frequency/1000, *ClockFrequencySelect));
}


VOID
GetCardConfigurationData (
  VOID
  )
{
  UINTN  BlockSize;
  UINTN  NumBlocks;
  UINTN  ClockFrequencySelect;

  //Calculate BlockSize and Total number of blocks in the detected card.
  GetBlockInformation(&BlockSize, &NumBlocks);
  gCardInfo.BlockSize = BlockSize;
  gCardInfo.NumBlocks = NumBlocks;

  //Calculate Card clock divider value.
  CalculateCardCLKD(&ClockFrequencySelect);
  gCardInfo.ClockFrequencySelect = ClockFrequencySelect;
}


EFI_STATUS
InitializeMMCHS (
  VOID
  )
{
  UINT8      Data = 0;
  EFI_STATUS Status;

  //Select Device group to belong to P1 device group in Power IC.
  Data = DEV_GRP_P1;
  Status = gTPS65950->Write (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID4, VMMC1_DEV_GRP), 1, &Data);
  ASSERT_EFI_ERROR(Status);

  //Configure voltage regulator for MMC1 in Power IC to output 3.0 voltage.
  Data = VSEL_3_00V;
  Status = gTPS65950->Write (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID4, VMMC1_DEDICATED_REG), 1, &Data);
  ASSERT_EFI_ERROR(Status);

  //After ramping up voltage, set VDDS stable bit to indicate that voltage level is stable.
  MmioOr32 (CONTROL_PBIAS_LITE, (PBIASLITEVMODE0 | PBIASLITEPWRDNZ0 | PBIASSPEEDCTRL0 | PBIASLITEVMODE1 | PBIASLITEWRDNZ1));

  // Enable WP GPIO
  MmioAndThenOr32 (GPIO1_BASE + GPIO_OE, ~BIT23, BIT23);

  // Enable Card Detect
  Data = CARD_DETECT_ENABLE;
  gTPS65950->Write (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID2, TPS65950_GPIO_CTRL), 1, &Data);


  return Status;
}


EFI_STATUS
PerformCardIdenfication (
  VOID
  )
{
  EFI_STATUS Status;
  UINTN      CmdArgument = 0;
  UINTN      Response = 0;
  UINTN      RetryCount = 0;
  BOOLEAN    SDCmd8Supported = FALSE;

  //Enable interrupts.
  MmioWrite32 (MMCHS_IE, (BADA_EN | CERR_EN | DEB_EN | DCRC_EN | DTO_EN | CIE_EN |
    CEB_EN | CCRC_EN | CTO_EN | BRR_EN | BWR_EN | TC_EN | CC_EN));

  //Controller INIT procedure start.
  MmioOr32 (MMCHS_CON, INIT);
  MmioWrite32 (MMCHS_CMD, 0x00000000);
  while (!(MmioRead32 (MMCHS_STAT) & CC));

  //Wait for 1 ms
  gBS->Stall(1000);

  //Set CC bit to 0x1 to clear the flag
  MmioOr32 (MMCHS_STAT, CC);

  //Retry INIT procedure.
  MmioWrite32 (MMCHS_CMD, 0x00000000);
  while (!(MmioRead32 (MMCHS_STAT) & CC));

  //End initialization sequence
  MmioAnd32 (MMCHS_CON, ~INIT);

  MmioOr32 (MMCHS_HCTL, (SDVS_3_0_V | DTW_1_BIT | SDBP_ON));

  //Change clock frequency to 400KHz to fit protocol
  UpdateMMCHSClkFrequency(CLKD_400KHZ);

  MmioOr32 (MMCHS_CON, OD);

  //Send CMD0 command.
  Status = SendCmd (CMD0, CMD0_INT_EN, CmdArgument);
  if (EFI_ERROR(Status)) {
    DEBUG ((EFI_D_ERROR, "Cmd0 fails.\n"));
    return Status;
  }

  DEBUG ((EFI_D_INFO, "CMD0 response: %x\n", MmioRead32 (MMCHS_RSP10)));

  //Send CMD5 command.
  Status = SendCmd (CMD5, CMD5_INT_EN, CmdArgument);
  if (Status == EFI_SUCCESS) {
    DEBUG ((EFI_D_ERROR, "CMD5 Success. SDIO card. Follow SDIO card specification.\n"));
    DEBUG ((EFI_D_INFO, "CMD5 response: %x\n", MmioRead32 (MMCHS_RSP10)));
    //NOTE: Returning unsupported error for now. Need to implement SDIO specification.
    return EFI_UNSUPPORTED;
  } else {
    DEBUG ((EFI_D_INFO, "CMD5 fails. Not an SDIO card.\n"));
  }

  MmioOr32 (MMCHS_SYSCTL, SRC);
  gBS->Stall(1000);
  while ((MmioRead32 (MMCHS_SYSCTL) & SRC));

  //Send CMD8 command. (New v2.00 command for Voltage check)
  //Only 2.7V - 3.6V is supported for SD2.0, only SD 2.0 card can pass.
  //MMC & SD1.1 card will fail this command.
  CmdArgument = CMD8_ARG;
  Status = SendCmd (CMD8, CMD8_INT_EN, CmdArgument);
  if (Status == EFI_SUCCESS) {
    Response = MmioRead32 (MMCHS_RSP10);
    DEBUG ((EFI_D_INFO, "CMD8 success. CMD8 response: %x\n", Response));
    if (Response != CmdArgument) {
      return EFI_DEVICE_ERROR;
    }
    DEBUG ((EFI_D_INFO, "Card is SD2.0\n"));
    SDCmd8Supported = TRUE; //Supports high capacity.
  } else {
    DEBUG ((EFI_D_INFO, "CMD8 fails. Not an SD2.0 card.\n"));
  }

  MmioOr32 (MMCHS_SYSCTL, SRC);
  gBS->Stall(1000);
  while ((MmioRead32 (MMCHS_SYSCTL) & SRC));

  //Poll till card is busy
  while (RetryCount < MAX_RETRY_COUNT) {
    //Send CMD55 command.
    CmdArgument = 0;
    Status = SendCmd (CMD55, CMD55_INT_EN, CmdArgument);
    if (Status == EFI_SUCCESS) {
      DEBUG ((EFI_D_INFO, "CMD55 success. CMD55 response: %x\n", MmioRead32 (MMCHS_RSP10)));
      gCardInfo.CardType = SD_CARD;
    } else {
      DEBUG ((EFI_D_INFO, "CMD55 fails.\n"));
      gCardInfo.CardType = MMC_CARD;
    }

    //Send appropriate command for the card type which got detected.
    if (gCardInfo.CardType == SD_CARD) {
      CmdArgument = ((UINTN *) &(gCardInfo.OCRData))[0];

      //Set HCS bit.
      if (SDCmd8Supported) {
        CmdArgument |= HCS;
      }

      Status = SendCmd (ACMD41, ACMD41_INT_EN, CmdArgument);
      if (EFI_ERROR(Status)) {
        DEBUG ((EFI_D_INFO, "ACMD41 fails.\n"));
        return Status;
      }
      ((UINT32 *) &(gCardInfo.OCRData))[0] = MmioRead32 (MMCHS_RSP10);
      DEBUG ((EFI_D_INFO, "SD card detected. ACMD41 OCR: %x\n", ((UINT32 *) &(gCardInfo.OCRData))[0]));
    } else if (gCardInfo.CardType == MMC_CARD) {
      CmdArgument = 0;
      Status = SendCmd (CMD1, CMD1_INT_EN, CmdArgument);
      if (EFI_ERROR(Status)) {
        DEBUG ((EFI_D_INFO, "CMD1 fails.\n"));
        return Status;
      }
      Response = MmioRead32 (MMCHS_RSP10);
      DEBUG ((EFI_D_INFO, "MMC card detected.. CMD1 response: %x\n", Response));

      //NOTE: For now, I am skipping this since I only have an SD card.
      //Compare card OCR and host OCR (Section 22.6.1.3.2.4)
      return EFI_UNSUPPORTED; //For now, MMC is not supported.
    }

    //Poll the card until it is out of its power-up sequence.
    if (gCardInfo.OCRData.Busy == 1) {

      if (SDCmd8Supported) {
        gCardInfo.CardType = SD_CARD_2;
      }

      //Card is ready. Check CCS (Card capacity status) bit (bit#30).
      //SD 2.0 standard card will response with CCS 0, SD high capacity card will respond with CCS 1.
      if (gCardInfo.OCRData.AccessMode & BIT1) {
        gCardInfo.CardType = SD_CARD_2_HIGH;
        DEBUG ((EFI_D_INFO, "High capacity card.\n"));
      } else {
        DEBUG ((EFI_D_INFO, "Standard capacity card.\n"));
      }

      break;
    }

    gBS->Stall(1000);
    RetryCount++;
  }

  if (RetryCount == MAX_RETRY_COUNT) {
    DEBUG ((EFI_D_ERROR, "Timeout error. RetryCount: %d\n", RetryCount));
    return EFI_TIMEOUT;
  }

  //Read CID data.
  CmdArgument = 0;
  Status = SendCmd (CMD2, CMD2_INT_EN, CmdArgument);
  if (EFI_ERROR(Status)) {
    DEBUG ((EFI_D_ERROR, "CMD2 fails. Status: %x\n", Status));
    return Status;
  }

  DEBUG ((EFI_D_INFO, "CMD2 response: %x %x %x %x\n", MmioRead32 (MMCHS_RSP10), MmioRead32 (MMCHS_RSP32), MmioRead32 (MMCHS_RSP54), MmioRead32 (MMCHS_RSP76)));

  //Parse CID register data.
  ParseCardCIDData(MmioRead32 (MMCHS_RSP10), MmioRead32 (MMCHS_RSP32), MmioRead32 (MMCHS_RSP54), MmioRead32 (MMCHS_RSP76));

  //Read RCA
  CmdArgument = 0;
  Status = SendCmd (CMD3, CMD3_INT_EN, CmdArgument);
  if (EFI_ERROR(Status)) {
    DEBUG ((EFI_D_ERROR, "CMD3 fails. Status: %x\n", Status));
    return Status;
  }

  //Set RCA for the detected card. RCA is CMD3 response.
  gCardInfo.RCA = (MmioRead32 (MMCHS_RSP10) >> 16);
  DEBUG ((EFI_D_INFO, "CMD3 response: RCA %x\n", gCardInfo.RCA));

  //MMC Bus setting change after card identification.
  MmioAnd32 (MMCHS_CON, ~OD);
  MmioOr32 (MMCHS_HCTL, SDVS_3_0_V);
  UpdateMMCHSClkFrequency(CLKD_400KHZ); //Set the clock frequency to 400KHz.

  return EFI_SUCCESS;
}


EFI_STATUS
GetCardSpecificData (
  VOID
  )
{
  EFI_STATUS Status;
  UINTN      CmdArgument;

  //Send CMD9 to retrieve CSD.
  CmdArgument = gCardInfo.RCA << 16;
  Status = SendCmd (CMD9, CMD9_INT_EN, CmdArgument);
  if (EFI_ERROR(Status)) {
    DEBUG ((EFI_D_ERROR, "CMD9 fails. Status: %x\n", Status));
    return Status;
  }

  //Populate 128-bit CSD register data.
  ((UINT32 *)&(gCardInfo.CSDData))[0] = MmioRead32 (MMCHS_RSP10);
  ((UINT32 *)&(gCardInfo.CSDData))[1] = MmioRead32 (MMCHS_RSP32);
  ((UINT32 *)&(gCardInfo.CSDData))[2] = MmioRead32 (MMCHS_RSP54);
  ((UINT32 *)&(gCardInfo.CSDData))[3] = MmioRead32 (MMCHS_RSP76);

  DEBUG ((EFI_D_INFO, "CMD9 response: %x %x %x %x\n", MmioRead32 (MMCHS_RSP10), MmioRead32 (MMCHS_RSP32), MmioRead32 (MMCHS_RSP54), MmioRead32 (MMCHS_RSP76)));

  //Calculate total number of blocks and max. data transfer rate supported by the detected card.
  GetCardConfigurationData();

  return Status;
}


EFI_STATUS
PerformCardConfiguration (
  VOID
  )
{
  UINTN      CmdArgument = 0;
  EFI_STATUS Status;

  //Send CMD7
  CmdArgument = gCardInfo.RCA << 16;
  Status = SendCmd (CMD7, CMD7_INT_EN, CmdArgument);
  if (EFI_ERROR(Status)) {
    DEBUG ((EFI_D_ERROR, "CMD7 fails. Status: %x\n", Status));
    return Status;
  }

  if ((gCardInfo.CardType != UNKNOWN_CARD) && (gCardInfo.CardType != MMC_CARD)) {
    // We could read SCR register, but SD Card Phys spec stats any SD Card shall
    // set SCR.SD_BUS_WIDTHS to support 4-bit mode, so why bother?

    // Send ACMD6 (application specific commands must be prefixed with CMD55)
    Status = SendCmd (CMD55, CMD55_INT_EN, CmdArgument);
    if (!EFI_ERROR (Status)) {
      // set device into 4-bit data bus mode
      Status = SendCmd (ACMD6, ACMD6_INT_EN, 0x2);
      if (!EFI_ERROR (Status)) {
        // Set host controler into 4-bit mode
        MmioOr32 (MMCHS_HCTL, DTW_4_BIT);
        DEBUG ((EFI_D_INFO, "SD Memory Card set to 4-bit mode\n"));
      }
    }
  }

  //Send CMD16 to set the block length
  CmdArgument = gCardInfo.BlockSize;
  Status = SendCmd (CMD16, CMD16_INT_EN, CmdArgument);
  if (EFI_ERROR(Status)) {
    DEBUG ((EFI_D_ERROR, "CMD16 fails. Status: %x\n", Status));
    return Status;
  }

  //Change MMCHS clock frequency to what detected card can support.
  UpdateMMCHSClkFrequency(gCardInfo.ClockFrequencySelect);

  return EFI_SUCCESS;
}


EFI_STATUS
ReadBlockData (
  IN  EFI_BLOCK_IO_PROTOCOL       *This,
  OUT VOID                        *Buffer
  )
{
  UINTN MmcStatus;
  UINTN *DataBuffer = Buffer;
  UINTN DataSize = This->Media->BlockSize/4;
  UINTN Count;
  UINTN RetryCount = 0;

  //Check controller status to make sure there is no error.
  while (RetryCount < MAX_RETRY_COUNT) {
    do {
      //Read Status.
      MmcStatus = MmioRead32 (MMCHS_STAT);
    } while(MmcStatus == 0);

    //Check if Buffer read ready (BRR) bit is set?
    if (MmcStatus & BRR) {

      //Clear BRR bit
      MmioOr32 (MMCHS_STAT, BRR);

      //Read block worth of data.
      for (Count = 0; Count < DataSize; Count++) {
        *DataBuffer++ = MmioRead32 (MMCHS_DATA);
      }
      break;
    }
    RetryCount++;
  }

  if (RetryCount == MAX_RETRY_COUNT) {
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}


EFI_STATUS
WriteBlockData (
  IN  EFI_BLOCK_IO_PROTOCOL       *This,
  OUT VOID                        *Buffer
  )
{
  UINTN MmcStatus;
  UINTN *DataBuffer = Buffer;
  UINTN DataSize = This->Media->BlockSize/4;
  UINTN Count;
  UINTN RetryCount = 0;

  //Check controller status to make sure there is no error.
  while (RetryCount < MAX_RETRY_COUNT) {
    do {
      //Read Status.
      MmcStatus = MmioRead32 (MMCHS_STAT);
    } while(MmcStatus == 0);

    //Check if Buffer write ready (BWR) bit is set?
    if (MmcStatus & BWR) {

      //Clear BWR bit
      MmioOr32 (MMCHS_STAT, BWR);

      //Write block worth of data.
      for (Count = 0; Count < DataSize; Count++) {
        MmioWrite32 (MMCHS_DATA, *DataBuffer++);
      }

      break;
    }
    RetryCount++;
  }

  if (RetryCount == MAX_RETRY_COUNT) {
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
DmaBlocks (
  IN EFI_BLOCK_IO_PROTOCOL        *This,
  IN  UINTN                       Lba,
  IN OUT VOID                     *Buffer,
  IN  UINTN                       BlockCount,
  IN  OPERATION_TYPE              OperationType
  )
{
  EFI_STATUS            Status;
  UINTN                 DmaSize = 0;
  UINTN                 Cmd = 0;
  UINTN                 CmdInterruptEnable;
  UINTN                 CmdArgument;
  VOID                  *BufferMap;
  EFI_PHYSICAL_ADDRESS  BufferAddress;
  OMAP_DMA4             Dma4;
  DMA_MAP_OPERATION     DmaOperation;
  EFI_STATUS            MmcStatus;
  UINTN                 RetryCount = 0;

CpuDeadLoop ();
  // Map passed in buffer for DMA xfer
  DmaSize = BlockCount * This->Media->BlockSize;
  Status = DmaMap (DmaOperation, Buffer, &DmaSize, &BufferAddress, &BufferMap);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  ZeroMem (&DmaOperation, sizeof (DMA_MAP_OPERATION));


  Dma4.DataType = 2;                      // DMA4_CSDPi[1:0]   32-bit elements from MMCHS_DATA

  Dma4.SourceEndiansim = 0;               // DMA4_CSDPi[21]

  Dma4.DestinationEndianism = 0;          // DMA4_CSDPi[19]

  Dma4.SourcePacked = 0;                  // DMA4_CSDPi[6]

  Dma4.DestinationPacked = 0;             // DMA4_CSDPi[13]

  Dma4.NumberOfElementPerFrame = This->Media->BlockSize/4; // DMA4_CENi  (TRM 4K is optimum value)

  Dma4.NumberOfFramePerTransferBlock = BlockCount;         // DMA4_CFNi

  Dma4.ReadPriority = 0;                  // DMA4_CCRi[6]      Low priority read

  Dma4.WritePriority = 0;                 // DMA4_CCRi[23]     Prefetech disabled


  //Populate the command information based on the operation type.
  if (OperationType == READ) {
    Cmd = CMD18; //Multiple block read
    CmdInterruptEnable = CMD18_INT_EN;
    DmaOperation = MapOperationBusMasterCommonBuffer;

    Dma4.ReadPortAccessType =0 ;            // DMA4_CSDPi[8:7]   Can not burst MMCHS_DATA reg

    Dma4.WritePortAccessType = 3;           // DMA4_CSDPi[15:14] Memory burst 16x32

    Dma4.WriteMode = 1;                     // DMA4_CSDPi[17:16] Write posted



    Dma4.SourceStartAddress = MMCHS_DATA;                   // DMA4_CSSAi

    Dma4.DestinationStartAddress = (UINT32)BufferAddress;   // DMA4_CDSAi

    Dma4.SourceElementIndex = 1;                            // DMA4_CSEi

    Dma4.SourceFrameIndex = 0x200;                          // DMA4_CSFi

    Dma4.DestinationElementIndex = 1;                       // DMA4_CDEi

    Dma4.DestinationFrameIndex = 0;                         // DMA4_CDFi



    Dma4.ReadPortAccessMode = 0;            // DMA4_CCRi[13:12]  Always read MMCHS_DATA

    Dma4.WritePortAccessMode = 1;           // DMA4_CCRi[15:14]  Post increment memory address

    Dma4.ReadRequestNumber = 0x1e;          // DMA4_CCRi[4:0]    Syncro with MMCA_DMA_RX (61)

    Dma4.WriteRequestNumber = 1;            // DMA4_CCRi[20:19]  Syncro upper 0x3e == 62 (one based)

  } else if (OperationType == WRITE) {
    Cmd = CMD25; //Multiple block write
    CmdInterruptEnable = CMD25_INT_EN;
    DmaOperation = MapOperationBusMasterRead;

    Dma4.ReadPortAccessType = 3;            // DMA4_CSDPi[8:7]   Memory burst 16x32

    Dma4.WritePortAccessType = 0;           // DMA4_CSDPi[15:14] Can not burst MMCHS_DATA reg

    Dma4.WriteMode = 1;                     // DMA4_CSDPi[17:16] Write posted ???



    Dma4.SourceStartAddress = (UINT32)BufferAddress;        // DMA4_CSSAi

    Dma4.DestinationStartAddress = MMCHS_DATA;              // DMA4_CDSAi

    Dma4.SourceElementIndex = 1;                            // DMA4_CSEi

    Dma4.SourceFrameIndex = 0x200;                          // DMA4_CSFi

    Dma4.DestinationElementIndex = 1;                       // DMA4_CDEi

    Dma4.DestinationFrameIndex = 0;                         // DMA4_CDFi



    Dma4.ReadPortAccessMode = 1;            // DMA4_CCRi[13:12]  Post increment memory address

    Dma4.WritePortAccessMode = 0;           // DMA4_CCRi[15:14]  Always write MMCHS_DATA

    Dma4.ReadRequestNumber = 0x1d;          // DMA4_CCRi[4:0]    Syncro with MMCA_DMA_TX (60)

    Dma4.WriteRequestNumber = 1;            // DMA4_CCRi[20:19]  Syncro upper 0x3d == 61 (one based)

  } else {
    return EFI_INVALID_PARAMETER;
  }


  EnableDmaChannel (2, &Dma4);


  //Set command argument based on the card access mode (Byte mode or Block mode)
  if (gCardInfo.OCRData.AccessMode & BIT1) {
    CmdArgument = Lba;
  } else {
    CmdArgument = Lba * This->Media->BlockSize;
  }

  //Send Command.
  Status = SendCmd (Cmd, CmdInterruptEnable, CmdArgument);
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "CMD fails. Status: %x\n", Status));
    return Status;
  }

    //Check for the Transfer completion.
  while (RetryCount < MAX_RETRY_COUNT) {
    //Read Status
    do {
      MmcStatus = MmioRead32 (MMCHS_STAT);
    } while (MmcStatus == 0);

    //Check if Transfer complete (TC) bit is set?
    if (MmcStatus & TC) {
      break;
    } else {
      DEBUG ((EFI_D_ERROR, "MmcStatus for TC: %x\n", MmcStatus));
      //Check if DEB, DCRC or DTO interrupt occured.
      if ((MmcStatus & DEB) | (MmcStatus & DCRC) | (MmcStatus & DTO)) {
        //There was an error during the data transfer.

        //Set SRD bit to 1 and wait until it return to 0x0.
        MmioOr32 (MMCHS_SYSCTL, SRD);
        while((MmioRead32 (MMCHS_SYSCTL) & SRD) != 0x0);

        DisableDmaChannel (2, DMA4_CSR_BLOCK, DMA4_CSR_ERR);
        DmaUnmap (BufferMap);
        return EFI_DEVICE_ERROR;
      }
    }
    RetryCount++;
  }

  DisableDmaChannel (2, DMA4_CSR_BLOCK, DMA4_CSR_ERR);
  Status = DmaUnmap (BufferMap);

  if (RetryCount == MAX_RETRY_COUNT) {
    DEBUG ((EFI_D_ERROR, "TransferBlockData timed out.\n"));
    return EFI_TIMEOUT;
  }

  return Status;
}


EFI_STATUS
TransferBlock (
  IN EFI_BLOCK_IO_PROTOCOL        *This,
  IN  UINTN                       Lba,
  IN OUT VOID                     *Buffer,
  IN  OPERATION_TYPE              OperationType
  )
{
  EFI_STATUS Status;
  UINTN      MmcStatus;
  UINTN      RetryCount = 0;
  UINTN      Cmd = 0;
  UINTN      CmdInterruptEnable = 0;
  UINTN      CmdArgument = 0;


  //Populate the command information based on the operation type.
  if (OperationType == READ) {
    Cmd = CMD17; //Single block read
    CmdInterruptEnable = CMD18_INT_EN;
  } else if (OperationType == WRITE) {
    Cmd = CMD24; //Single block write
    CmdInterruptEnable = CMD24_INT_EN;
  }

  //Set command argument based on the card access mode (Byte mode or Block mode)
  if (gCardInfo.OCRData.AccessMode & BIT1) {
    CmdArgument = Lba;
  } else {
    CmdArgument = Lba * This->Media->BlockSize;
  }

  //Send Command.
  Status = SendCmd (Cmd, CmdInterruptEnable, CmdArgument);
  if (EFI_ERROR(Status)) {
    DEBUG ((EFI_D_ERROR, "CMD fails. Status: %x\n", Status));
    return Status;
  }

  //Read or Write data.
  if (OperationType == READ) {
    Status = ReadBlockData (This, Buffer);
    if (EFI_ERROR(Status)) {
      DEBUG((EFI_D_ERROR, "ReadBlockData fails.\n"));
      return Status;
    }
  } else if (OperationType == WRITE) {
    Status = WriteBlockData (This, Buffer);
    if (EFI_ERROR(Status)) {
      DEBUG((EFI_D_ERROR, "WriteBlockData fails.\n"));
      return Status;
    }
  }

  //Check for the Transfer completion.
  while (RetryCount < MAX_RETRY_COUNT) {
    //Read Status
    do {
      MmcStatus = MmioRead32 (MMCHS_STAT);
    } while (MmcStatus == 0);

    //Check if Transfer complete (TC) bit is set?
    if (MmcStatus & TC) {
      break;
    } else {
      DEBUG ((EFI_D_ERROR, "MmcStatus for TC: %x\n", MmcStatus));
      //Check if DEB, DCRC or DTO interrupt occured.
      if ((MmcStatus & DEB) | (MmcStatus & DCRC) | (MmcStatus & DTO)) {
        //There was an error during the data transfer.

        //Set SRD bit to 1 and wait until it return to 0x0.
        MmioOr32 (MMCHS_SYSCTL, SRD);
        while((MmioRead32 (MMCHS_SYSCTL) & SRD) != 0x0);

        return EFI_DEVICE_ERROR;
      }
    }
    RetryCount++;
  }

  if (RetryCount == MAX_RETRY_COUNT) {
    DEBUG ((EFI_D_ERROR, "TransferBlockData timed out.\n"));
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}

BOOLEAN
CardPresent (
  VOID
  )
{
  EFI_STATUS  Status;
  UINT8       Data;

  //
  // Card detect is a GPIO0 on the TPS65950
  //
  Status = gTPS65950->Read (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID2, GPIODATAIN1), 1, &Data);
  if (EFI_ERROR (Status)) {
    return FALSE;
  }

  if ((Data & CARD_DETECT_BIT) == CARD_DETECT_BIT) {
    // No Card present
    return FALSE;
  } else {
    return TRUE;
  }
}

EFI_STATUS
DetectCard (
  VOID
  )
{
  EFI_STATUS    Status;

  if (!CardPresent ()) {
    return EFI_NO_MEDIA;
  }

  //Initialize MMC host controller clocks.
  Status = InitializeMMCHS ();
  if (EFI_ERROR(Status)) {
    DEBUG ((EFI_D_ERROR, "Initialize MMC host controller fails. Status: %x\n", Status));
    return Status;
  }

  //Software reset of the MMCHS host controller.
  MmioWrite32 (MMCHS_SYSCONFIG, SOFTRESET);
  gBS->Stall(1000);
  while ((MmioRead32 (MMCHS_SYSSTATUS) & RESETDONE_MASK) != RESETDONE);

  //Soft reset for all.
  MmioWrite32 (MMCHS_SYSCTL, SRA);
  gBS->Stall(1000);
  while ((MmioRead32 (MMCHS_SYSCTL) & SRA) != 0x0);

  //Voltage capabilities initialization. Activate VS18 and VS30.
  MmioOr32 (MMCHS_CAPA, (VS30 | VS18));

  //Wakeup configuration
  MmioOr32 (MMCHS_SYSCONFIG, ENAWAKEUP);
  MmioOr32 (MMCHS_HCTL, IWE);

  //MMCHS Controller default initialization
  MmioOr32 (MMCHS_CON, (OD | DW8_1_4_BIT | CEATA_OFF));

  MmioWrite32 (MMCHS_HCTL, (SDVS_3_0_V | DTW_1_BIT | SDBP_OFF));

  //Enable internal clock
  MmioOr32 (MMCHS_SYSCTL, ICE);

  //Set the clock frequency to 80KHz.
  UpdateMMCHSClkFrequency (CLKD_80KHZ);

  //Enable SD bus power.
  MmioOr32 (MMCHS_HCTL, (SDBP_ON));

  //Poll till SD bus power bit is set.
  while ((MmioRead32 (MMCHS_HCTL) & SDBP_MASK) != SDBP_ON);

  //Card idenfication
  Status = PerformCardIdenfication ();
  if (EFI_ERROR(Status)) {
    DEBUG ((EFI_D_ERROR, "No MMC/SD card detected.\n"));
    return Status;
  }

  //Get CSD (Card specific data) for the detected card.
  Status = GetCardSpecificData();
  if (EFI_ERROR(Status)) {
    return Status;
  }

  //Configure the card in data transfer mode.
  Status = PerformCardConfiguration();
  if (EFI_ERROR(Status)) {
    return Status;
  }

  //Patch the Media structure.
  gMMCHSMedia.LastBlock    = (gCardInfo.NumBlocks - 1);
  gMMCHSMedia.BlockSize    = gCardInfo.BlockSize;
  gMMCHSMedia.ReadOnly     = (MmioRead32 (GPIO1_BASE + GPIO_DATAIN) & BIT23) == BIT23;
  gMMCHSMedia.MediaPresent = TRUE;
  gMMCHSMedia.MediaId++;

  DEBUG ((EFI_D_INFO, "SD Card Media Change on Handle 0x%08x\n", gImageHandle));

  return Status;
}

#define MAX_MMCHS_TRANSFER_SIZE  0x4000

EFI_STATUS
SdReadWrite (
  IN EFI_BLOCK_IO_PROTOCOL    *This,
  IN  UINTN                   Lba,
  OUT VOID                    *Buffer,
  IN  UINTN                   BufferSize,
  IN  OPERATION_TYPE          OperationType
  )
{
  EFI_STATUS Status = EFI_SUCCESS;
  UINTN      RetryCount = 0;
  UINTN      BlockCount;
  UINTN      BytesToBeTranferedThisPass = 0;
  UINTN      BytesRemainingToBeTransfered;
  EFI_TPL    OldTpl;

  BOOLEAN    Update;



  Update               = FALSE;

  if (gMediaChange) {
    Update = TRUE;
    Status = DetectCard  ();
    if (EFI_ERROR (Status)) {
      // We detected a removal
      gMMCHSMedia.MediaPresent = FALSE;
      gMMCHSMedia.LastBlock    = 0;
      gMMCHSMedia.BlockSize    = 512;  // Should be zero but there is a bug in DiskIo
      gMMCHSMedia.ReadOnly     = FALSE;
    }
    gMediaChange             = FALSE;
  } else if (!gMMCHSMedia.MediaPresent) {
    Status = EFI_NO_MEDIA;
    goto Done;
  }

  if (Update) {
    DEBUG ((EFI_D_INFO, "SD Card ReinstallProtocolInterface ()\n"));
    gBS->ReinstallProtocolInterface (
          gImageHandle,
          &gEfiBlockIoProtocolGuid,
          &gBlockIo,
          &gBlockIo
          );
    return EFI_MEDIA_CHANGED;
  }

  if (EFI_ERROR (Status)) {
    goto Done;
  }

  if (Buffer == NULL) {
    Status = EFI_INVALID_PARAMETER;
    goto Done;
  }

  if (Lba > This->Media->LastBlock) {
    Status = EFI_INVALID_PARAMETER;
    goto Done;
  }

  if ((BufferSize % This->Media->BlockSize) != 0) {
    Status = EFI_BAD_BUFFER_SIZE;
    goto Done;
  }

  //Check if the data lines are not in use.
  while ((RetryCount++ < MAX_RETRY_COUNT) && ((MmioRead32 (MMCHS_PSTATE) & DATI_MASK) != DATI_ALLOWED));
  if (RetryCount == MAX_RETRY_COUNT) {
    Status = EFI_TIMEOUT;
    goto Done;
  }

  OldTpl = gBS->RaiseTPL (TPL_NOTIFY);

  BytesRemainingToBeTransfered = BufferSize;
  while (BytesRemainingToBeTransfered > 0) {

    if (gMediaChange) {
      Status = EFI_NO_MEDIA;
      DEBUG ((EFI_D_INFO, "SdReadWrite() EFI_NO_MEDIA due to gMediaChange\n"));
      goto DoneRestoreTPL;
    }

    // Turn OFF DMA path until it is debugged
    // BytesToBeTranferedThisPass = (BytesToBeTranferedThisPass >= MAX_MMCHS_TRANSFER_SIZE) ? MAX_MMCHS_TRANSFER_SIZE : BytesRemainingToBeTransfered;
    BytesToBeTranferedThisPass   = This->Media->BlockSize;

    BlockCount = BytesToBeTranferedThisPass/This->Media->BlockSize;

    if (BlockCount > 1) {
      Status = DmaBlocks (This, Lba, Buffer, BlockCount, OperationType);
    } else {
      //Transfer a block worth of data.
      Status = TransferBlock (This, Lba, Buffer, OperationType);
    }

    if (EFI_ERROR(Status)) {
      DEBUG ((EFI_D_ERROR, "TransferBlockData fails. %x\n", Status));
      goto DoneRestoreTPL;
    }

    BytesRemainingToBeTransfered -= BytesToBeTranferedThisPass;
    Lba    += BlockCount;
    Buffer = (UINT8 *)Buffer + This->Media->BlockSize;
  }

DoneRestoreTPL:

  gBS->RestoreTPL (OldTpl);

Done:

  return Status;

}


/**

  Reset the Block Device.



  @param  This                 Indicates a pointer to the calling context.

  @param  ExtendedVerification Driver may perform diagnostics on reset.



  @retval EFI_SUCCESS          The device was reset.

  @retval EFI_DEVICE_ERROR     The device is not functioning properly and could

                               not be reset.



**/
EFI_STATUS
EFIAPI
MMCHSReset (
  IN EFI_BLOCK_IO_PROTOCOL          *This,
  IN BOOLEAN                        ExtendedVerification
  )
{
  return EFI_SUCCESS;
}


/**

  Read BufferSize bytes from Lba into Buffer.



  @param  This       Indicates a pointer to the calling context.

  @param  MediaId    Id of the media, changes every time the media is replaced.

  @param  Lba        The starting Logical Block Address to read from

  @param  BufferSize Size of Buffer, must be a multiple of device block size.

  @param  Buffer     A pointer to the destination buffer for the data. The caller is

                     responsible for either having implicit or explicit ownership of the buffer.



  @retval EFI_SUCCESS           The data was read correctly from the device.

  @retval EFI_DEVICE_ERROR      The device reported an error while performing the read.

  @retval EFI_NO_MEDIA          There is no media in the device.

  @retval EFI_MEDIA_CHANGED     The MediaId does not matched the current device.

  @retval EFI_BAD_BUFFER_SIZE   The Buffer was not a multiple of the block size of the device.

  @retval EFI_INVALID_PARAMETER The read request contains LBAs that are not valid,

                                or the buffer is not on proper alignment.

EFI_STATUS

**/
EFI_STATUS
EFIAPI
MMCHSReadBlocks (
  IN EFI_BLOCK_IO_PROTOCOL          *This,
  IN UINT32                         MediaId,
  IN EFI_LBA                        Lba,
  IN UINTN                          BufferSize,
  OUT VOID                          *Buffer
  )
{
  EFI_STATUS Status;

  //Perform Read operation.
  Status = SdReadWrite (This, (UINTN)Lba, Buffer, BufferSize, READ);

  return Status;

}


/**

  Write BufferSize bytes from Lba into Buffer.



  @param  This       Indicates a pointer to the calling context.

  @param  MediaId    The media ID that the write request is for.

  @param  Lba        The starting logical block address to be written. The caller is

                     responsible for writing to only legitimate locations.

  @param  BufferSize Size of Buffer, must be a multiple of device block size.

  @param  Buffer     A pointer to the source buffer for the data.



  @retval EFI_SUCCESS           The data was written correctly to the device.

  @retval EFI_WRITE_PROTECTED   The device can not be written to.

  @retval EFI_DEVICE_ERROR      The device reported an error while performing the write.

  @retval EFI_NO_MEDIA          There is no media in the device.

  @retval EFI_MEDIA_CHNAGED     The MediaId does not matched the current device.

  @retval EFI_BAD_BUFFER_SIZE   The Buffer was not a multiple of the block size of the device.

  @retval EFI_INVALID_PARAMETER The write request contains LBAs that are not valid,

                                or the buffer is not on proper alignment.



**/
EFI_STATUS
EFIAPI
MMCHSWriteBlocks (
  IN EFI_BLOCK_IO_PROTOCOL          *This,
  IN UINT32                         MediaId,
  IN EFI_LBA                        Lba,
  IN UINTN                          BufferSize,
  IN VOID                           *Buffer
  )
{
  EFI_STATUS  Status;

  //Perform write operation.
  Status = SdReadWrite (This, (UINTN)Lba, Buffer, BufferSize, WRITE);


  return Status;

}


/**

  Flush the Block Device.



  @param  This              Indicates a pointer to the calling context.



  @retval EFI_SUCCESS       All outstanding data was written to the device

  @retval EFI_DEVICE_ERROR  The device reported an error while writting back the data

  @retval EFI_NO_MEDIA      There is no media in the device.



**/
EFI_STATUS
EFIAPI
MMCHSFlushBlocks (
  IN EFI_BLOCK_IO_PROTOCOL  *This
  )
{
  return EFI_SUCCESS;
}


EFI_BLOCK_IO_PROTOCOL gBlockIo = {
  EFI_BLOCK_IO_INTERFACE_REVISION,   // Revision
  &gMMCHSMedia,                      // *Media
  MMCHSReset,                        // Reset
  MMCHSReadBlocks,                   // ReadBlocks
  MMCHSWriteBlocks,                  // WriteBlocks
  MMCHSFlushBlocks                   // FlushBlocks
};


/**

  Timer callback to convert card present hardware into a boolean that indicates

  a media change event has happened. If you just check the GPIO you could see

  card 1 and then check again after card 1 was removed and card 2 was inserted

  and you would still see media present. Thus you need the timer tick to catch

  the toggle event.



  @param  Event                 Event whose notification function is being invoked.

  @param  Context               The pointer to the notification function's context,

                                which is implementation-dependent. Not used.



**/
VOID
EFIAPI
TimerCallback (
  IN  EFI_EVENT   Event,
  IN  VOID        *Context
  )
{
  BOOLEAN Present;

  Present = CardPresent ();
  if (gMMCHSMedia.MediaPresent) {
    if (!Present && !gMediaChange) {
      gMediaChange = TRUE;
    }
  } else {
    if (Present && !gMediaChange) {
      gMediaChange = TRUE;
    }
  }
}


EFI_STATUS
EFIAPI
MMCHSInitialize (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS  Status;

  Status = gBS->LocateProtocol (&gEmbeddedExternalDeviceProtocolGuid, NULL, (VOID **)&gTPS65950);
  ASSERT_EFI_ERROR(Status);

  ZeroMem (&gCardInfo, sizeof (CARD_INFO));

  Status = gBS->CreateEvent (EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, TimerCallback, NULL, &gTimerEvent);
  ASSERT_EFI_ERROR (Status);

  Status = gBS->SetTimer (gTimerEvent, TimerPeriodic, FixedPcdGet32 (PcdMmchsTimerFreq100NanoSeconds));
  ASSERT_EFI_ERROR (Status);

  //Publish BlockIO.
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &ImageHandle,
                  &gEfiBlockIoProtocolGuid,    &gBlockIo,
                  &gEfiDevicePathProtocolGuid, &gMmcHsDevicePath,
                  NULL
                  );
  return Status;
}
